/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.hamacekj.geometr;

import cz.hamacekj.geometr.plugin.IPoint;
import cz.hamacekj.geometr.plugin.InputParametersInterface;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.print.PageFormat;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;

/**
 * Základní třída grafického rozhraní. Tvoří základní část View části MVC.
 * @author hamacekh
 */
public class UI {
    
    private List<ActionListener> saveListeners = new ArrayList<>();
    private List<ActionListener> blankListeners = new ArrayList<>();
    private List<ActionListener> openListeners = new ArrayList<>();
    private List<ActionListener> printListeners = new ArrayList<>();
    private List<ActionListener> printStepListListeners = new ArrayList<>();
    private List<PropertyChangeListener> propertyListeners = new ArrayList<>();
    private List<ConstructObjectListener> constructObjectListeners = new ArrayList<>();
    // toolbar a jeho layout. Budu udrzovat konvenci, ze rozcestnik toolbaru je vzdy
    // na prvni karte.
    private CardLayout toolbarLayout;
    private JPanel toolbar;
    private JFrame rootFrame;
    /**
     * Dvojice jméno pluginu - jeho UI komponenta.
     */
    Map<String, UIConstructionObject> constructionObjects =
            Collections.synchronizedMap(new HashMap<String, UIConstructionObject>());
    /**
     * Tlacitko ke smazani posledniho kroku.
     */
    private JButton deleteLast;
    /**
     * Model zobrazovany v postupu konstrukce a vykreslovany vykreslovacem.
     */
    StepsModel stepsModel;
    
    /**
     * Současný výběr kroků. Označuje do kterého kroku se má vykreslovat.
     */
     ListSelectionModel stepsSelection;
     
     /**
     * Hodnota, která určuje jestli se vůbec bude vykreslovat nákres.
     */
     private boolean paintEnabled;
     
     /**
      * Painter pro kresleni nakresu.
      */
     ConstructionPainter painter;
     
     /**
      * Komponenta, na kterou se kreslí.
      */
     PaintingComponent paintingComponent;
     
     /**
      * Komponenta postupu konstrukce.
      */
     JList<Step> stepsListView;
    
    // --- SEKCE TVORBY GUI
    
    /**
     * Vytvoří GUI.
     */
    public void createAndShowGUI(){
        rootFrame = new JFrame("Geometr");
        rootFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        rootFrame.setExtendedState(rootFrame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
        Container root = rootFrame.getContentPane();
        rootFrame.setJMenuBar(createMenuBar());
        root.add(createToolBar(), BorderLayout.NORTH);
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        splitPane.setLeftComponent(createStepList());
        paintingComponent = createPaintArea();
        splitPane.setRightComponent(paintingComponent);
        root.add(splitPane, BorderLayout.CENTER);
        this.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if("paint".equals(evt.getPropertyName()) && evt.getNewValue() instanceof Boolean){
                    painter.setPaint((Boolean)evt.getNewValue());
                    UI.this.repaint();
                }
            }
        });
        rootFrame.setPreferredSize(new Dimension(1000, 700));
        rootFrame.pack();
        rootFrame.setVisible(true);
    }
    
    private PaintingComponent createPaintArea(){
        painter = new ConstructionPainter(this.stepsModel,
                this.stepsSelection, 100, new Point(-1, -1));
        PaintingComponent paintingComponent = new PaintingComponent(painter);
        PainterMouseListener mouseListener = new PainterMouseListener();
        paintingComponent.addMouseListener(mouseListener);
        PainterMouseWheelListener mouseWheelListener = new PainterMouseWheelListener();
        paintingComponent.addMouseWheelListener(mouseWheelListener);
        return paintingComponent;
        
    }
    
    private JComponent createStepList(){
        assert stepsModel != null;
        JPanel panel = new JPanel(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        stepsListView = new JList<>(stepsModel);
        stepsListView.setSelectionBackground(Color.YELLOW);
        stepsListView.setCellRenderer(new ListStepRenderer(this.constructionObjects));
        this.stepsSelection = stepsListView.getSelectionModel();
        this.stepsSelection.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        c.weighty = 1;
        c.weightx = 1;
        c.fill = GridBagConstraints.BOTH;
        panel.add(stepsListView, c);
        deleteLast = new JButton("Smazat poslední");
        c.weighty = 0;
        c.gridy = 1;
        panel.add(deleteLast, c);
        
        JCheckBox paintEnabledCheckbox = new JCheckBox("Zobrazit nákres", true);
        paintEnabledCheckbox.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) {
                PropertyChangeEvent ev = null;
                if(e.getStateChange() == ItemEvent.DESELECTED){
                    ev = new PropertyChangeEvent(UI.this, "paint", Boolean.TRUE, Boolean.FALSE);
                    paintEnabled = false;
                }
                if(e.getStateChange() == ItemEvent.SELECTED){
                    ev = new PropertyChangeEvent(UI.this, "paint", Boolean.FALSE, Boolean.TRUE);
                    paintEnabled = true;
                }
                if(ev == null){
                    return;
                }
                UI.this.firePropertyChangeEvent(ev);
            }
        });
        c.gridy = 2;
        panel.add(paintEnabledCheckbox, c);
        return panel;
    }
    
    protected void firePropertyChangeEvent(PropertyChangeEvent e){
        for( PropertyChangeListener l : propertyListeners){
            l.propertyChange(e);
        }
    }
    
    /**
     * Vytvoří toolbar.
     * @return panel toolbaru.
     */
    private JPanel createToolBar(){
        toolbarLayout = new CardLayout();
        toolbar = new JPanel(toolbarLayout);
        JPanel firstCard = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
        firstCard.add(new JLabel("Nový objekt:"));
        toolbar.add(firstCard, "default");
        for(final UIConstructionObject ob: constructionObjects.values()){
            setupConstructionObject(ob, firstCard);
        }
        Dimension size = toolbar.getPreferredSize();
        size.height = (int)(size.height * 2);
        toolbar.setPreferredSize(size);
        
        return toolbar;
    }
    
    private JMenuBar createMenuBar(){
        JMenuBar menu = new JMenuBar();
        JMenu file = new JMenu("Soubor");
        JMenuItem blank = new JMenuItem("Nový postup");
        blank.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                fireBlankEvent(e);
            }
        });
        JMenuItem save = new JMenuItem("Uložit");
        save.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireSaveEvent(e);
            }
        });
        JMenuItem saveAs = new JMenuItem("Uložit jako");
        saveAs.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                ActionEvent event = new ActionEvent(e.getSource(), e.getID(), "SaveAs");
                fireSaveEvent(event);
            }
        });
        JMenuItem open = new JMenuItem("Otevřít");
        open.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireOpenEvent(e);
            }
        });
        JMenuItem print = new JMenuItem("Tisk nákresu");
        print.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                firePrintEvent(e);
            }
        });
        JMenuItem printList = new JMenuItem("Tisk postupu konstrukce");
        printList.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                firePrintListEvent(e);
            }
        });
        JMenu options = new JMenu("Nástroje");
        JCheckBoxMenuItem printPreview = new JCheckBoxMenuItem("Zobrazit tisknutou oblast");
        printPreview.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) {
                boolean selected = e.getStateChange() == ItemEvent.SELECTED ? true : false;
                painter.setPrintableAreaVisible(selected);
                repaint();
            }
        });
        JCheckBoxMenuItem printLandscape = new JCheckBoxMenuItem("Náhled na šířku");
        printLandscape.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) {
                PageFormat page = painter.getPageFormat();
                if(e.getStateChange() == ItemEvent.SELECTED){
                    page.setOrientation(PageFormat.LANDSCAPE);
                }else{
                    page.setOrientation(PageFormat.PORTRAIT);
                }
                repaint();
                
            }
        });
        JMenu printMenu = new JMenu("Tisk");
        JMenuItem zoomIn = new JMenuItem("Přiblížit");
        zoomIn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                UI.this.zoomIn();
            }
        });
        JMenuItem zoomOut = new JMenuItem("Oddálit");
        zoomOut.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                UI.this.zoomOut();
            }
        });
        options.add(zoomIn);
        options.add(zoomOut);
        printMenu.add(printPreview);
        printMenu.add(printLandscape);
        printMenu.addSeparator();
        printMenu.add(print);
        printMenu.add(printList);
        file.add(blank);
        file.add(open);
        file.add(save);
        file.add(saveAs);
        menu.add(file);
        menu.add(printMenu);
        menu.add(options);
        return menu;
    }
    
    // ---- KONEC SEKCE TVORBY GUI
    
    /**
     * Zobrazí otázku. Mělo by běžet ve swink vlákně.
     * @param quesion Otázka
     * @return true, pokud odpovězeno kladně. Jinak false.
     */
    public boolean showQuestion(String quesion){
        return showQuestion(quesion, "Otázka");
    }
    
    /**
     * Zobrazí otázku. Mělo by běžet ve swing vláknu.
     * @param question Otázka.
     * @param title Titulek.
     * @return true pokud odpovězeno ano jinak false
     */
    public boolean showQuestion(String title, String question){
        int res = JOptionPane.showConfirmDialog(this.rootFrame,
                question, title, JOptionPane.YES_NO_OPTION);
        return res == JOptionPane.YES_OPTION ? true : false;
    }
    
    /**
     * Zobrazí zprávu. Může být volána mimo swing vlákno.
     * @param message Zpráva.
     * @param title titulek.
     */
    public void showMessage(final String title, final String message){
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JOptionPane.showMessageDialog(UI.this.rootFrame,
                        message, title, JOptionPane.INFORMATION_MESSAGE);
            }
        });
    }
    
    /**
     * Zobrazí dialog k uložení souboru.
     * @return File objekt otevřeného souboru nebo null, pokud nic nebylo vybráno.
     */
    public File showSaveDialog(){
        JFileChooser dialog = new JFileChooser();
        dialog.setMultiSelectionEnabled(false);
        FileNameExtensionFilter filter = new FileNameExtensionFilter("*.gtr", "gtr");
        dialog.setFileFilter(filter);
        int res = dialog.showSaveDialog(this.rootFrame);
        if(res == JFileChooser.APPROVE_OPTION){
            File f = dialog.getSelectedFile();
            if(!f.getName().endsWith(".gtr")){
                f = new File(f.toString()+".gtr");
            }
            if(!f.exists()){
                return f;
            }
            if(f.exists() && this.showQuestion("Potvrďte přepsání",
                    "Vybraný soubor již existuje, chcete jej přepsat?")){
                return f;
            }
        }
        return null;
    }
    
    /**
     * Zobrazí dialog k otevření souboru.
     * @return Soubor, který byl vybrán uživatelem nebo null,
     * pokud žádný nebyl vybrán nebo byl dialog zrušen.
     */
    public File showLoadDialog(){
        JFileChooser dialog = new JFileChooser();
        dialog.setMultiSelectionEnabled(false);
        FileNameExtensionFilter filter = new FileNameExtensionFilter("*.gtr", "gtr");
        dialog.setFileFilter(filter);
        int res = dialog.showOpenDialog(this.rootFrame);
        if(res == JFileChooser.APPROVE_OPTION){
            return dialog.getSelectedFile();
        }
        return null;
    }
    
    /**
     * Přidá do UI možnost vytvoření objektu s name, ikonou a vstupní řádkou definovanou polem params.
     * Další volání této funkce se stejným argumentem name nic neudělá.
     * @param name Jméno vytvářeného objektu.
     * @param icon Ikona vytvářeného objektu.
     * @param params Pole všech přípustných řádek parametrů.
     * @throws IndexOutOfBoundsException pokud neexistuje první panel toolbaru.
     * @throws ClassCastException pokud první panel toolbaru není {@link JPanel JPanel}
     */
    public void addConstructionObject(String name, ImageIcon icon, InputParametersInterface[] params){
        UIConstructionObject ob = new UIConstructionObject(name, icon, params);
        ob.setToolbar(toolbar, toolbarLayout);
        ob.setConstructionObjectListeners(constructObjectListeners);
        this.constructionObjects.put(name, ob);
        setupConstructionObject(ob, (JPanel) toolbar.getComponent(0));
    }
    
    /**
     * Skryje možnost přidání objektu pluginName pomocí parametru paramsName.
     * @param pluginName Jméno pluginu, jehož parametr bude skryt.
     * @param paramsName Jméno parametru, který bude skryt.
     */
    public void hideInputParameters(final String pluginName, final String paramsName){
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                UIConstructionObject ob = UI.this.constructionObjects.get(pluginName);
                ob.hideParameter(paramsName);
            }
        });
        
        
    }
    
    /**
     * Zobrazí možnost přidání objektu pluginName pomocí parametru paramsName.
     * @param pluginName Jméno pluginu, jehož parametr bude zobrazen.
     * @param paramsName Jméno parametru, který bude skryt.
     */
    public void showInputParameters(final String pluginName, final String paramsName){
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                UIConstructionObject ob = UI.this.constructionObjects.get(pluginName);
                ob.showParameter(paramsName);
            }
        });
    }
    
    
    /**
     * Listenery, které budou zavolány po vyvolání dialogu uložení souboru.
     * {@link ActionEvent#getActionCommand() ActionCommand} předaný při spuštění
     * těchto listenerů obsahuje "SaveAs" pokud jde o příkaz uložit jako.
     * @param l 
     */
    public void addSaveListener(ActionListener l){
        saveListeners.add(l);
    }
    
    /**
     * Listenery, které budou zavolány po vyvolání dialogu otevření souboru.
     * @param l 
     */
    public void addOpenListener(ActionListener l){
        openListeners.add(l);
    }
    
    /**
     * Přidá listener, který bude zavolaný po kliknutí na "tisk".
     * @param l 
     */
    public void addPrintListener(ActionListener l){
        printListeners.add(l);
    }
    
    /**
     * Přidá listenery, které budou zavoláno po kliknutí na tisk postupu konstrukce.
     * @param l 
     */
    public void addPrintListListener(ActionListener l){
        printStepListListeners.add(l);
    }
    
    /**
     * Přidá listener, který bude zavolán vždy když uživatel zadá příkaz k vytvoření objektu.
     * @param l 
     */
    public void addConstructObjectListener(ConstructObjectListener l){
        constructObjectListeners.add(l);
    }
    
    /**
     * Přidá listener, který bude zavolán vždy když je vyvoláno vytvoření nového projektu.
     * @param l listener
     */
    public void addBlankListener(ActionListener l){
        blankListeners.add(l);
    }
    
    /**
     * Přidá listener, který bude zavolán vždy když uživatel zmáčkne tlačítko
     * smazat poslední krok.
     * @param l 
     */
    public void addDeleteLastListener(ActionListener l){
        this.deleteLast.addActionListener(l);
    }
    
    /**
     * Přidá listener na změnu atributu. Atributy, které se mohou měnit:
     * <ul>
     * <li> paint - boolean jestli bude vykreslován nákres. </li>
     * </ul>
     * @param e 
     */
    public void addPropertyChangeListener(PropertyChangeListener e){
        this.propertyListeners.add(e);
    }
    
    /**
     * Nastaví model kroků, které budou vykreslovány.
     * @param model Model kroků, které budou vykreslovány.
     */
    public void setStepModel(StepsModel model){
        this.stepsModel = model;
    }
    
    /**
     * Listenery, které budou notifikovány vždy, když bude změněn výběr v seznamu
     * kroků konstrukce.
     * @param l 
     */
    public void addStepSelectionListener(ListSelectionListener l){
        this.stepsSelection.addListSelectionListener(l);
    }
    
    /**
     * Nastaví krok, který bude vybraný.
     * @param index 
     */
    public void setSelectedStep(int index){
        this.stepsSelection.setSelectionInterval(0, index);
    }
    
    /**
     * Vrátí index vybraného kroku nebo -1 pokud není žádný vybraný.
     * @return index vybraného kroku.
     */
    public int getSelectedStepIndex(){
        return this.stepsSelection.getMaxSelectionIndex();
    }
    
    /**
     * Překreslí plochu nákresu. Vyvolá přepočítání pozice popisků.
     */
    public void repaint(){
        painter.refreshLabelPlacement();
        paintingComponent.repaint();
    }
    
    public void showError(final String title,final String error){
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JOptionPane.showMessageDialog(rootFrame, error, title, JOptionPane.ERROR_MESSAGE);
            }
        });
        
    }
    
    private void movePaintArea(java.awt.Point diference){
        throw new UnsupportedOperationException();
    }
    
    /**
     * Přiblíží nákres se středem současné pozice o 1 jednotku.
     * @see #zoomIn(java.awt.Point, int) 
     */
    private void zoomIn(){
        this.zoomIn(null, 1);
    }
    
    /**
     * Přiblíží nákres se středem současné pozice o 1 jednotku.
     * @see #zoomOut(java.awt.Point, int) 
     */
    private void zoomOut(){
        this.zoomOut(null, 1);
    }
    
    /**
     * Přiblíží nákres podle středu o zadané množství jednotek.
     * @param center 
     * @param amount 
     */
    private void zoomIn(java.awt.Point center, int amount){
        double lengthRatio = painter.getLengthRatio();
        for(int i = 0; i < amount; i++){
            lengthRatio = lengthRatio - lengthRatio/8;
        }
        //Point p1 = calcPos(center);
        // zakomentovana cast je pokus o uchovani souradnice mysi na svem miste,
        // opravim to pozdeji
        painter.setLengthRatio(lengthRatio);
        /*Point p2 = calcPos(center);
        IPoint start = painter.getRelativeStart();
        Point newStart = new Point(start.getX()+ (p2.getX() - p1.getY()),
                start.getY()+ (p2.getY() - p1.getY()));
        painter.setRelativeStart(newStart);*/
        this.repaint();
    }
    
    /**
     * Oddálí nákres podle středu o zadané množství jednotek.
     * @param center 
     * @param amount 
     */
    private void zoomOut(java.awt.Point center, int amount){
        double lengthRatio = painter.getLengthRatio();
        for(int i = 0; i < amount; i++){
            lengthRatio = lengthRatio + lengthRatio/8;
        }
        painter.setLengthRatio(lengthRatio);
        this.repaint();
    }
    
    private cz.hamacekj.geometr.Point calcPos(java.awt.Point pos){
        return new Point(pos.x / painter.getLengthRatio(), pos.y / painter.getLengthRatio());
    }
    
    
    /**
     * Bude se starat o posouvání 
     */
    private class PainterMouseListener implements java.awt.event.MouseListener{
        private java.awt.Point position = null;
        @Override
        public void mouseClicked(MouseEvent e) {
            // do nothing
        }

        @Override
        public void mousePressed(MouseEvent e) {
            position = e.getPoint();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            // paint area will was moved. Compute new position, note it into painter and repaint.
            if(position == null){
                return;
            }
            int dX = e.getX() - position.x;
            int dY = e.getY() - position.y;
            double rX = dX / painter.getLengthRatio();
            double rY = dY / painter.getLengthRatio();
            IPoint p = painter.getRelativeStart();
            IPoint newRelativeStart = new Point(p.getX()- rX, p.getY() - rY);
            painter.setRelativeStart(newRelativeStart);
            position = null;
            UI.this.repaint();
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            // do nothing
        }

        @Override
        public void mouseExited(MouseEvent e) {
            // do nothing
        }
        
    }
    
    /**
     * Třída obsluhující přibližování a oddalování pomocí scrollovacího kolečka.
     */
    private class PainterMouseWheelListener implements MouseWheelListener{

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            // handle zoom via mouse wheel
            int rotation = e.getWheelRotation();
            if(rotation > 0){
                zoomIn(e.getPoint(), rotation);
            }
            if(rotation < 0){
                zoomOut(e.getPoint(), -rotation);
            }
        }
        
    }
    
    private class PaintingComponent extends JPanel{

        ConstructionPainter painter;
        
        public PaintingComponent(ConstructionPainter p) {
            super();
            this.painter = p;
            this.setDoubleBuffered(true);
            this.setBackground(Color.WHITE);
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            painter.paint((Graphics2D)g, null, this.getWidth(), this.getHeight());
        }
        
    }
    
    // -- ZACATEK POMOCNYCH PRIVATNICH FUNKCI
    
    /**
     * Přidá constuctionObject do panelu. Buttony na první kartu a odpovídající karty do toolbaru.
     * @param ob ConstructionObject k přidání
     * @param firstCard JPanel, na který přijdou buttony
     */
    private void setupConstructionObject(final UIConstructionObject ob, JPanel firstCard){
        toolbar.add(ob.getConstructionPanel(), ob.getName());
            JButton button = new JButton();
            if(ob.getIcon() != null){
                button.setIcon(ob.getIcon());
                button.setText(ob.getName());
            }else{
                button.setText(ob.getName());
            }
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    toolbarLayout.show(toolbar, ob.getName());
                }
            });
            ob.setMyButton(button);
            firstCard.add(button);
    }
    
    protected void fireSaveEvent(ActionEvent e){
        for(ActionListener l: saveListeners){
            l.actionPerformed(e);
        }
    }
    
    protected void fireBlankEvent(ActionEvent e){
        for(ActionListener l: blankListeners){
            l.actionPerformed(e);
        }
    }
    
    protected void fireOpenEvent(ActionEvent e){
        for(ActionListener l: openListeners){
            l.actionPerformed(e);
        }
    }
    
    protected void firePrintEvent(ActionEvent e){
        for(ActionListener l: printListeners){
            l.actionPerformed(e);
        }
    }
    
    protected void firePrintListEvent(ActionEvent e){
        for(ActionListener l : printStepListListeners){
            l.actionPerformed(e);
        }
    }
    
    protected void fireConstructObjectEvent(ConstructObjectEvent e){
        for(ConstructObjectListener l: constructObjectListeners){
            l.objectConstructed(e);
        }
    }
    
}
